[iOS] 左右のマージン部分で前後のバナーが少し見えているカルーセルUIを作りたい
こんにちは。きんくまです。
今回は前後が少し見えているバナー(カルーセルUI)を作りたい。です。
つくったもの
仕様
- 横スワイプでページをきりかえできる
- バナーの左右に、前のバナーと後ろのバナーが少し見える
- 画面下のページコントロールがバナーの位置に連動
- ページコントロールでもバナーをきりかえ
- 端末のサイズに応じて、比率で幅や高さを適用させる
- バナーをタップするイベントを取得して、どのバナーがタップされたかわかる
- バナーをスクロールして先頭に戻すことが可能
ソースコード
GitHubにアップしました
cm-tsmaeda / BannerViewSample
カスタマイズ
BannerScrollViewControllerのこのあたりは、デザイン上の値を設定します。
そうすると、その値と端末の横幅から比率を計算してレイアウトします。
/// バナーエリアの全体の幅 let baseComponentWidth: CGFloat = 406 /// デザインでのパネルの幅 let basePanelWidth: CGFloat = 355 /// デザインでのパネルの高さ let basePanelImageHeight: CGFloat = 238 /// デザインでのテキスト部分の高さ let basePanelTextContainerHeight: CGFloat = 30 /// デザインでのパネルの横マージン(先頭の左マージン) lazy var basePanelHorizontalMargin: CGFloat = (baseComponentWidth - basePanelWidth) / 2 /// デザインでのパネル間のマージン let basePanelGap: CGFloat = 8
実装のポイント
ページング
UIScrollViewの isPagingEnabled
をtrueにすると、ページングが可能になります。
ただし、ScrollViewのframeに合わせて表示されるので、前後はそのままだと表示されません。
なので、 clipsToBounds
をfalseにすることで、見えていなかった部分が見えるようになります。
しかしここで問題がおきます。
frameの外にある部分は当たり判定(hitTest)が機能しないのです。
で、それを解決しているのが以下の部分です。
BannerScrollViewController.swift
// frameの外はタッチイベントがきかない。その回避策 // https://stackoverflow.com/a/36641652 view.addGestureRecognizer(scrollView.panGestureRecognizer)
現在のページindexをもとめる
ScrollViewのcontentOffsetに応じて、現在のページindexを計算します。indexが変わったら自作のdelegateにイベントを発行します。
BannerScrollViewController.swift
func scrollViewDidScroll(_ scrollView: UIScrollView) { let currentIndex = pageIndex let newIndex = calcPageIndex(contentOffsetX: scrollView.contentOffset.x) if currentIndex != newIndex { pageIndex = newIndex delegate?.bannerScrollViewController(self, didChangePageIndex: newIndex) } } /// 現在のページindexを返す func calcPageIndex(contentOffsetX: CGFloat) -> Int { // 0.5 - 1.5 の間は1で返す // 1.5 - 2.5 の間は2で返す.... return Int(round(contentOffsetX / (panelWidth + panelGap))) }
UIPageControlとの連動
- UIScrollViewでページがきりかわったらUIPageControlをきりかえる
- UIPageControlの値がかわったら、UIScrollViewのページをきりかえる
ということをやります。
ViewController.swift
func updateBannerPageControl() { bannerPageControl.currentPage = bannerViewController.pageIndex } @IBAction func didChangeBannerPageControl() { bannerViewController.showPage(index: bannerPageControl.currentPage, animated: true) } @IBAction func didTapResetButton() { bannerViewController.showPage(index: 0, animated: true) } func bannerScrollViewController(_ scrollViewController: BannerScrollViewController, didChangePageIndex index: Int) { updateBannerPageControl() }
まとめ
バナーつくってみました。ではでは。
余談
実は一番ひっかかったところは、xibファイルとUIViewのクラスファイルのひもづけですw
(今回のメイン部分とは全く無関係)
- BannerView.xib
- BannerView.swift(UIViewのサブクラスで、BannerView.xibのClass設定で使う)
- BannerViewController.swift(BannerView.xibのClass設定では使わない)
という3つのファイルがあって、BannerViewをUINibからinitしたら実行時エラーがおきてクラッシュしまくり。
なんでかわからず、とほうにくれていたら、BannerViewをinitするときに、同じ名前のViewControllerがあると暗黙的にそっちにリンクすることを知りました。
そんなことを知らんがなーw
なので、BannerViewController.swiftの名前をBannerScrollViewControllerに変更しました。
気をつけよう!命名規則!!